/*
 * Copyright 2019 Sergey Ivanov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sergivonavi.materialbanner;

import com.sergivonavi.materialbanner.internal.ButtonsContainer;
import com.sergivonavi.materialbanner.internal.MessageView;
import com.sergivonavi.materialbanner.utils.AttrUtils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.Animator.StateChangedListener;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.*;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;

import java.io.IOException;

public class Banner extends ComponentContainer implements BannerInterface,
        Component.EstimateSizeListener, ComponentContainer.ArrangeListener {
    private static final int              ANIM_DURATION_DISMISS = 160;
    private static final int              ANIM_DURATION_SHOW    = 180;
    private static final int              LAYOUT_UNDEFINED      = -1;
    private static final int              LAYOUT_SINGLE_LINE    = 0;
    private static final int              LAYOUT_MULTILINE      = 1;
    private              int              mContainerPaddingTopOneLine;
    private              int              mContainerPaddingTopMultiline;
    private              int              mIconSize;
    private              int              mIconMarginStart;
    private              int              mMessageMarginStart;
    private              int              mMessageMarginEndSingleLine;
    private              int              mMessageMarginEndMultiline;
    private              int              mMessageMarginBottomMultiline;
    private              int              mMessageMarginBottomWithIcon;
    private              int              mLineHeight;
    private              int              mButtonMarginEnd;
    private              int              mButtonMarginBottom;
    private              DependentLayout  mContentContainer;
    private              Image            mIconView;
    private              MessageView      mMessageView;
    private              ButtonsContainer mButtonsContainer;
    private              Button           mLeftButton;
    private              Button           mRightButton;
    private              Component        mLine;
    private              Element          mIcon;
    private              String           mMessageText;
    private              String           mLeftButtonText;
    private              String           mRightButtonText;

    private BannerInterface.OnClickListener   mLeftButtonListener;
    private BannerInterface.OnClickListener   mRightButtonListener;
    private BannerInterface.OnDismissListener mOnDismissListener;
    private BannerInterface.OnShowListener    mOnShowListener;

    /**
     * Banner's bottom margin.
     */
    private int mMarginBottom;

    /**
     * Indicates that the device is at least a 10-inch tablet.
     */
    private boolean mWideLayout;

    /**
     * The layout type: {@link #LAYOUT_UNDEFINED} {@link #LAYOUT_SINGLE_LINE} or
     * {@link #LAYOUT_MULTILINE}.
     */
    private int mLayoutType = LAYOUT_UNDEFINED;


    public Banner(Context context) {
        this(context, null);
    }

    public Banner(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    public Banner(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        loadDimens(context);
        initViewGroup(context);
        retrieveAttrs(context, attrSet, styleName);
    }

    private void loadDimens(Context context) {
        mIconSize = getDimen(ResourceTable.Integer_mb_icon_size);
        mIconMarginStart = getDimen(ResourceTable.Integer_mb_icon_margin_start);
        mMessageMarginStart = getDimen(ResourceTable.Integer_mb_message_margin_start);
        mMessageMarginEndSingleLine = getDimen(ResourceTable.Integer_mb_message_margin_end_singleline);
        mMessageMarginEndMultiline = getDimen(ResourceTable.Integer_mb_message_margin_end_multiline);
        mMessageMarginBottomMultiline = getDimen(ResourceTable.Integer_mb_message_margin_bottom_multiline);
        mMessageMarginBottomWithIcon = getDimen(ResourceTable.Integer_mb_message_margin_bottom_with_icon);
        mLineHeight = getDimen(ResourceTable.Integer_mb_line_height);
        mContainerPaddingTopOneLine = getDimen(ResourceTable.Integer_mb_container_padding_top_singleline);
        mContainerPaddingTopMultiline = getDimen(ResourceTable.Integer_mb_container_padding_top_multiline);
        mButtonMarginEnd = getDimen(ResourceTable.Integer_mb_button_margin_end);
        mButtonMarginBottom = getDimen(ResourceTable.Integer_mb_button_margin_bottom);
    }

    private void initViewGroup(Context context) {
        // CONTENT CONTAINER
        LayoutConfig layoutConfig = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT);
        mContentContainer = new DependentLayout(context);
        mContentContainer.setId(ResourceTable.String_mb_container_content);
        mContentContainer.setLayoutConfig(layoutConfig);

        // ICON VIEW
        DependentLayout.LayoutConfig relativeLayoutParams = new DependentLayout.LayoutConfig(mIconSize, mIconSize);
        relativeLayoutParams.setMarginLeft(mIconMarginStart);
        relativeLayoutParams.addRule(
                DependentLayout.LayoutConfig.ALIGN_PARENT_START, DependentLayout.LayoutConfig.TRUE);

        mIconView = new Image(context);
        mIconView.setId(ResourceTable.String_mb_icon);
        mIconView.setLayoutConfig(relativeLayoutParams);
        mIconView.setVisibility(HIDE);

        // MESSAGE VIEW
        relativeLayoutParams = new DependentLayout.LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT);
        relativeLayoutParams.setMarginLeft(mMessageMarginStart);
        relativeLayoutParams.addRule(
                DependentLayout.LayoutConfig.ALIGN_PARENT_START, DependentLayout.LayoutConfig.TRUE);

        mMessageView = new MessageView(context);
        mMessageView.setId(ResourceTable.String_mb_message);
        mMessageView.setLayoutConfig(relativeLayoutParams);
        mMessageView.setMultipleLine(true);
        mMessageView.setTextSize(getDimen(ResourceTable.Integer_mb_message_text_size));

        // BUTTONS CONTAINER
        relativeLayoutParams = new DependentLayout.LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT);
        relativeLayoutParams.addRule(
                DependentLayout.LayoutConfig.ALIGN_PARENT_END, DependentLayout.LayoutConfig.TRUE);

        mButtonsContainer = new ButtonsContainer(context);
        mButtonsContainer.setId(ResourceTable.String_mb_container_buttons);
        mButtonsContainer.setLayoutConfig(relativeLayoutParams);

        mButtonsContainer.setMarginBottom(mButtonMarginBottom);
        mLeftButton = mButtonsContainer.getLeftButton();
        mRightButton = mButtonsContainer.getRightButton();

        // LINE
        layoutConfig = new LayoutConfig(LayoutConfig.MATCH_PARENT, mLineHeight);

        mLine = new Component(context);
        mLine.setId(ResourceTable.String_mb_line);
        mLine.setLayoutConfig(layoutConfig);

        // add View
        addComponent(mContentContainer);
        addComponent(mLine);

        // add content
        mContentContainer.addComponent(mIconView);
        mContentContainer.addComponent(mMessageView);
        mContentContainer.addComponent(mButtonsContainer);

        // 测量
        setEstimateSizeListener(this);

        // 布局
        setArrangeListener(this);
    }

    private void retrieveAttrs(Context context, AttrSet attrSet, String styleName) {
        if (null != attrSet) {
            setIcon(AttrUtils.getString(attrSet, "icon", null));

            setIconTintColorInternal(AttrUtils.getColor(attrSet, "iconTint", 0));

            setMessage(AttrUtils.getString(attrSet, "messageText", null));

            setLeftButton(AttrUtils.getString(attrSet,
                    "buttonLeftText", null), null);

            setRightButton(AttrUtils.getString(attrSet,
                    "buttonRightText", null), null);

            mMessageView.setBackground(AttrUtils.getElement(attrSet,
                    "messageTextAppearance", null));

            Element textAppearance = AttrUtils.getElement(attrSet,
                    "buttonsTextAppearance", null);
            mLeftButton.setBackground(textAppearance);
            mRightButton.setBackground(textAppearance);

            int color = AttrUtils.getColor(attrSet,
                    "messageTextColor", Color.BLACK.getValue());
            mMessageView.setTextColor(new Color(color));

            int buttonsTextColor = AttrUtils.getColor(attrSet,
                    "buttonsTextColor", context.getColor(ResourceTable.Color_bg));
            mLeftButton.setTextColor(new Color(buttonsTextColor));
            mRightButton.setTextColor(new Color(buttonsTextColor));

            setButtonTextIsBold(AttrUtils.getBoolean(attrSet,
                    "buttonsTextIsBold", false));

            setLeftButtonsRippleColor(AttrUtils.getElement(attrSet,
                    "leftButtonsRippleColor", null));
            setRightButtonsRippleColor(AttrUtils.getElement(attrSet,
                    "rightButtonsRippleColor", null));

            setBackground(AttrUtils.getElement(attrSet,
                    "backgroundColor", null));

            setLineColor(AttrUtils.getColor(attrSet,
                    "lineColor", Color.BLACK.getValue()));

            int contentPaddingStart = AttrUtils.getInteger(attrSet,
                    "contentPaddingStart", 0);
            int contentPaddingEnd = AttrUtils.getInteger(attrSet,
                    "contentPaddingEnd", 0);

            setContainerPadding(getDimenValue(contentPaddingStart),
                    -1, getDimenValue(contentPaddingEnd));
        }
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecSize = EstimateSpec.getSize(widthMeasureSpec);
        int widthUsed = EstimateSpec.getSize(getContainerHorizontalPadding());

        // Measure the message view
        mMessageView.estimateSize(widthMeasureSpec, heightMeasureSpec);

        // Adding the start margin and possible single line end margin
        int messageViewWidth = EstimateSpec.getSize(
                mMessageView.getEstimatedWidth() + mMessageMarginStart + mMessageMarginEndSingleLine);

        // Measure the icon
        int iconViewWidth = 0;
        if (mIcon != null) {
            mIconView.estimateSize(mIconSize, mIconSize);
            iconViewWidth = EstimateSpec.getSize(mIconView.getWidth() + mIconMarginStart);
        }
        mButtonsContainer.estimateSize(widthMeasureSpec, heightMeasureSpec);
        int buttonsWidth = EstimateSpec.getSize(mButtonsContainer.getEstimatedWidth());

        // Update the layout params
        if (widthSpecSize - widthUsed - iconViewWidth - buttonsWidth >= messageViewWidth) {
            // The message view fits in one line with the icon and the both buttons
            onSingleLine();
        } else {
            // Doesn't fit
            onMultiline();
        }

        mContentContainer.estimateSize(widthMeasureSpec, heightMeasureSpec);
        int lineHeight = EstimateSpec.getSizeWithMode(mLineHeight, EstimateSpec.PRECISE);
        mLine.estimateSize(widthMeasureSpec, lineHeight);
        widthUsed = mContentContainer.getEstimatedWidth();
        int heightUsed = 0;
        heightUsed = mContentContainer.getEstimatedHeight() + mLine.getEstimatedHeight();
        setEstimatedSize(widthUsed, heightUsed);
        return true;
    }

    private void onSingleLine() {
        if (mLayoutType == LAYOUT_SINGLE_LINE) {
            // Skip unnecessary layout params changes. The views already have the correct ones.
            return;
        }
        setContainerPadding(-1, mContainerPaddingTopOneLine, -1);
        LayoutConfig messageViewLayoutConfig = mMessageView.getLayoutConfig();
        if (messageViewLayoutConfig instanceof ComponentContainer.LayoutConfig) {
            DependentLayout.LayoutConfig messageLayoutParams =
                    (DependentLayout.LayoutConfig) messageViewLayoutConfig;
            messageLayoutParams.addRule(DependentLayout.LayoutConfig.START_OF, mButtonsContainer.getId());
            messageLayoutParams.setMarginRight(mMessageMarginEndSingleLine);
            messageLayoutParams.addRule(DependentLayout.LayoutConfig.ALIGN_BASELINE, mButtonsContainer.getId());
            messageLayoutParams.setMarginBottom(0);
            mMessageView.setLayoutConfig(messageLayoutParams);
        }
        LayoutConfig buttonsLayoutConfig = mButtonsContainer.getLayoutConfig();
        if (buttonsLayoutConfig instanceof ComponentContainer.LayoutConfig) {
            DependentLayout.LayoutConfig buttonsContainerLayoutParams =
                    (DependentLayout.LayoutConfig) buttonsLayoutConfig;
            buttonsContainerLayoutParams.addRule(DependentLayout.LayoutConfig.ALIGN_BASELINE, 0);
            buttonsContainerLayoutParams.addRule(DependentLayout.LayoutConfig.BELOW, 0);
            mButtonsContainer.setLayoutConfig(buttonsContainerLayoutParams);
        }
        mLayoutType = LAYOUT_SINGLE_LINE;
    }

    private void onMultiline() {
        if (mLayoutType == LAYOUT_MULTILINE) {
            // Skip unnecessary layout params changes. The views already have the correct ones.
            return;
        }
        setContainerPadding(-1, mContainerPaddingTopMultiline, -1);
        LayoutConfig messageLayoutConfig = mMessageView.getLayoutConfig();
        if (messageLayoutConfig instanceof ComponentContainer.LayoutConfig) {
            DependentLayout.LayoutConfig messageLayoutParams =
                    (DependentLayout.LayoutConfig) messageLayoutConfig;
            messageLayoutParams.addRule(DependentLayout.LayoutConfig.START_OF, 0);
            messageLayoutParams.setMarginBottom(mIcon == null ?
                    mMessageMarginBottomMultiline : mMessageMarginBottomWithIcon);
            messageLayoutParams.addRule(DependentLayout.LayoutConfig.ALIGN_BASELINE, 0);
            mMessageView.setLayoutConfig(messageLayoutParams);
        }
        LayoutConfig buttonLayoutConfig = mButtonsContainer.getLayoutConfig();
        if (buttonLayoutConfig instanceof ComponentContainer.LayoutConfig) {
            DependentLayout.LayoutConfig buttonsContainerLayoutParams =
                    (DependentLayout.LayoutConfig) buttonLayoutConfig;
            buttonsContainerLayoutParams.addRule(DependentLayout.LayoutConfig.BELOW, mMessageView.getId());
            mButtonsContainer.setLayoutConfig(buttonsContainerLayoutParams);
        }
        mLayoutType = LAYOUT_MULTILINE;
    }

    private void updateParamsOnIconChanged() {
        LayoutConfig mMessageViewLayoutConfig = mMessageView.getLayoutConfig();
        if (mMessageViewLayoutConfig instanceof ComponentContainer.LayoutConfig) {
            DependentLayout.LayoutConfig messageLayoutParams =
                    (DependentLayout.LayoutConfig) mMessageViewLayoutConfig;
            int parentStart = mIcon == null ? DependentLayout.LayoutConfig.TRUE : 0;
            int toEndOfId = mIcon == null ? 0 : mIconView.getId();
            messageLayoutParams.addRule(DependentLayout.LayoutConfig.ALIGN_PARENT_START, parentStart);
            messageLayoutParams.addRule(DependentLayout.LayoutConfig.END_OF, toEndOfId);
            mMessageView.setLayoutConfig(messageLayoutParams);
        }
    }


    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        int height = mContentContainer.getEstimatedHeight();
        mContentContainer.arrange(0, 0, mContentContainer.getEstimatedWidth(), height);
        mLine.arrange(0, height, mLine.getEstimatedWidth(), mLine.getEstimatedHeight());
        return true;
    }

    private void setIcon(String res) {
        if ((res == null) || (!res.contains("$media:"))) {
            return;
        }
        String tabIconSrc = res.replace("$media:", "");
        int resourceId = 0;
        Element imageElement = null;
        resourceId = Integer.parseInt(tabIconSrc);
        imageElement = new VectorElement(getContext(), resourceId);
        mIcon = imageElement;
        if (mIcon != null) {
            mIconView.setVisibility(VISIBLE);
            mIconView.setImageElement(imageElement);
        } else {
            mIconView.setVisibility(HIDE);
        }
        updateParamsOnIconChanged();
    }

    public void setIcon(int iconId, boolean isPng) {
        Element element;
        try {
            if (isPng) {
                element = new PixelMapElement(getContext().getResourceManager().getResource(iconId));
            } else {
                element = new VectorElement(getContext(), iconId);
            }
            mIcon = element;
            if (mIcon != null) {
                mIconView.setVisibility(VISIBLE);
                mIconView.setImageElement(element);
            } else {
                mIconView.setVisibility(HIDE);
            }
            updateParamsOnIconChanged();

        } catch (IOException | NotExistException e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the message to display in the banner.
     *
     * @param text The text to display in the banner
     */
    public void setMessage(String text) {
        mMessageText = text;
        mMessageView.setText(text);
    }

    /**
     * Sets the message to display in the banner using the given resource id.
     *
     * @param textId The resource id of the text to display
     */
    public void setMessage(int textId) {
        setMessage(getContext().getString(textId));
    }

    /**
     * Sets a listener to be invoked when the left button of the banner is pressed.
     * <p>
     * Usually used for the dismissive action.
     * </p>
     *
     * @param text     The text to display in the left button
     * @param listener The {@link BannerInterface.OnClickListener} to use
     * @see #setLeftButton(int, BannerInterface.OnClickListener)
     */
    public void setLeftButton(String text, BannerInterface.OnClickListener listener) {
        mLeftButtonText = text;
        if (mLeftButtonText != null) {
            mLeftButton.setVisibility(VISIBLE);
            mLeftButton.setText(text);
            setLeftButtonListener(listener);
        } else {
            mLeftButton.setVisibility(HIDE);
        }
    }

    /**
     * Sets a listener to be invoked when the left button of the banner is pressed.
     * <p>
     * Usually used for the dismissive action.
     * </p>
     *
     * @param textId   The resource id of the text to display in the left button
     * @param listener The {@link BannerInterface.OnClickListener} to use
     * @see #setLeftButton(String, BannerInterface.OnClickListener)
     */
    public void setLeftButton(
            int textId, BannerInterface.OnClickListener listener) {
        setLeftButton(getContext().getString(textId), listener);
    }

    /**
     * Sets a listener to be invoked when the left button of the banner is pressed.
     * <p>
     * Usually used for the dismissive action.
     * </p>
     *
     * @param listener The {@link BannerInterface.OnClickListener} to use
     */
    public void setLeftButtonListener(BannerInterface.OnClickListener listener) {
        mLeftButtonListener = listener;
        mLeftButton.setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {
                if (mLeftButtonListener != null) {
                    mLeftButtonListener.onClick(Banner.this);
                }
            }
        });
    }

    /**
     * Sets a listener to be invoked when the right button of the banner is pressed.
     * <p>
     * Usually used for the confirming action.
     * </p>
     *
     * @param text     The text to display in the right button
     * @param listener The {@link BannerInterface.OnClickListener} to use
     * @see #setRightButton(int, BannerInterface.OnClickListener)
     */
    public void setRightButton(String text, BannerInterface.OnClickListener listener) {
        mRightButtonText = text;
        if (mRightButtonText != null) {
            mRightButton.setVisibility(VISIBLE);
            mRightButton.setText(text);
            setRightButtonListener(listener);
        } else {
            mRightButton.setVisibility(HIDE);
        }
    }

    public void setButtonTextIsBold(boolean isBold) {
        if (isBold) {
            mLeftButton.setFont(Font.DEFAULT_BOLD);
            mRightButton.setFont(Font.DEFAULT_BOLD);
        }
    }

    /**
     * Sets a listener to be invoked when the right button of the banner is pressed.
     * <p>
     * Usually used for the confirming action.
     * </p>
     *
     * @param textId   The resource id of the text to display in the right button
     * @param listener The {@link BannerInterface.OnClickListener} to use
     * @see #setRightButton(String, BannerInterface.OnClickListener)
     */
    public void setRightButton(
            int textId, BannerInterface.OnClickListener listener) {
        setRightButton(getContext().getString(textId), listener);
    }

    /**
     * Sets a listener to be invoked when the right button of the banner is pressed.
     * <p>
     * Usually used for the confirming action.
     * </p>
     *
     * @param listener The {@link BannerInterface.OnClickListener} to use
     */
    public void setRightButtonListener(BannerInterface.OnClickListener listener) {
        mRightButtonListener = listener;
        mRightButton.setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {
                if (mRightButtonListener != null) {
                    mRightButtonListener.onClick(Banner.this);
                }
            }
        });
    }

    /**
     * Sets a listener to be invoked when the banner is dismissed.
     *
     * @param listener The {@link BannerInterface.OnDismissListener} to use
     */
    public void setOnDismissListener(BannerInterface.OnDismissListener listener) {
        mOnDismissListener = listener;
    }

    /**
     * Sets a listener to be invoked when the banner is shown.
     *
     * @param listener The {@link BannerInterface.OnShowListener} to use
     */
    public void setOnShowListener(BannerInterface.OnShowListener listener) {
        mOnShowListener = listener;
    }

    /**
     * Applies a tint to the icon.
     *
     * @param colorId the resource id of the color
     */
    public void setIconTintColor(int colorId) {
        try {
            setIconTintColorInternal(getContext().getResourceManager().getElement(colorId).getColor());
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
    }

    private void setIconTintColorInternal(int color) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(color));
        mIconView.setBackground(shapeElement);
    }

    /**
     * Sets the text color of a message.
     *
     * @param colorId the resource id of the color
     */
    public void setMessageTextColor(int colorId) {
        try {
            int color = getResourceManager().getElement(colorId).getColor();
            mMessageView.setTextColor(new Color(color));
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the text appearance of buttons' text from the specified style resource.
     *
     * @param resId The resource identifier of the style to apply.
     */
    public void setButtonsTextAppearance(int resId) {
        try {
            int color = getResourceManager().getElement(resId).getColor();
            ShapeElement shapeElement = new ShapeElement();
            shapeElement.setRgbColor(RgbColor.fromArgbInt(color));
            mLeftButton.setBackground(shapeElement);
            mRightButton.setBackground(shapeElement);
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the text color of both buttons.
     *
     * @param colorId the resource id of the color
     */
    public void setButtonsTextColor(int colorId) {
        try {
            int color = getResourceManager().getElement(colorId).getColor();
            mLeftButton.setTextColor(new Color(color));
            mRightButton.setTextColor(new Color(color));
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the ripple color for both buttons.
     *
     * @param resId the resource id of the color
     */
    public void setLeftButtonsRippleColor(int resId) {
        Element element = ElementScatter.getInstance(mContext).parse(resId);
        setLeftButtonsRippleColor(element);
    }

    public void setLeftButtonsRippleColor(Element element) {
        Element resultElement = null;
        if (element instanceof PixelMapElement) {
            resultElement = (PixelMapElement) element;
        } else if (element instanceof VectorElement) {
            resultElement = (VectorElement) element;
        } else {
            resultElement = element;
        }
        mLeftButton.setBackground(resultElement);
    }

    public void setRightButtonsRippleColor(int resId) {
        Element element = ElementScatter.getInstance(mContext).parse(resId);
        setRightButtonsRippleColor(element);
    }

    public void setRightButtonsRippleColor(Element element) {
        Element resultElement = null;
        if (element instanceof PixelMapElement) {
            resultElement = (PixelMapElement) element;
        } else if (element instanceof VectorElement) {
            resultElement = (VectorElement) element;
        } else {
            resultElement = element;
        }
        mRightButton.setBackground(resultElement);
    }

    public void setLineColor(int color) {
        ShapeElement shapeElement = new ShapeElement();
        shapeElement.setRgbColor(RgbColor.fromArgbInt(color));
        mLine.setBackground(shapeElement);
    }

    /**
     * Sets the opacity of the line to a value from 0 to 1, where 0 means the line is
     * completely transparent and 1 means the line is completely opaque.
     *
     * @param lineOpacity the opacity of the line
     */
    public void setLineOpacity(float lineOpacity) {
        mLine.setAlpha(lineOpacity);
    }

    /**
     * Sets a content start padding.
     *
     * @param dimenId the resource id of the dimension
     * @see #setContentPaddingStartPx(int)
     */
    public void setContentPaddingStart(int dimenId) {
        setContentPaddingStartPx(getDimen(dimenId));
    }

    /**
     * Sets a content start padding.
     *
     * @param dimenPx the padding in pixels
     * @see #setContentPaddingStart(int)
     */
    public void setContentPaddingStartPx(int dimenPx) {
        setContainerPadding(dimenPx, -1, -1);
    }

    /**
     * Sets a content end padding.
     *
     * @param dimenId the resource id of the dimension
     * @see #setContentPaddingEndPx(int)
     */
    public void setContentPaddingEnd(int dimenId) {
        setContentPaddingEndPx(getDimen(dimenId));
    }

    /**
     * Sets a content end padding.
     *
     * @param dimenPx the padding in pixels
     * @see #setContentPaddingEnd(int)
     */
    public void setContentPaddingEndPx(int dimenPx) {
        setContainerPadding(-1, -1, dimenPx);
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
    }

    /**
     * Sets the visibility state of this banner.
     * <p>
     * This will trigger {@link BannerInterface.OnShowListener} callback if visibility set to
     * {@link #VISIBLE} or {@link BannerInterface.OnDismissListener} callback if set to
     * {@link #HIDE}.
     * </p>
     * <p>
     * If visibility set to {@link #INVISIBLE} none of these callbacks will be triggered.
     * </p>
     *
     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #HIDE}.
     * @see #setVisibility(int)
     */
    public void setBannerVisibility(int visibility) {
        if (visibility == VISIBLE) {
            dispatchOnShow();
        } else {
            dispatchOnDismiss();
        }
        setVisibility(visibility);
    }

    /**
     * Shows the {@link Banner} with the animation.
     * <p>
     * Call {@link #setVisibility(int) Banner.setVisibility(VISIBLE)} to immediately show the
     * banner without animation.
     * </p>
     *
     * @see #setBannerVisibility(int)
     */
    public void show() {
        show(0);
    }

    /**
     * Shows the {@link Banner} with the animation after the specified delay in milliseconds.
     * <p>
     * Note that the delay should always be non-negative. Any negative delay will be clamped to 0
     * on N and above.
     * </p>
     * <p>
     * Call {@link #setVisibility(int) Banner.setVisibility(VISIBLE)} to immediately show the
     * banner without animation.
     * </p>
     *
     * @param delay The amount of time, in milliseconds, to delay starting the banner animation
     * @see #show()
     * @see #setBannerVisibility(int)
     */
    public void show(long delay) {
        // Other variants return getMeasuredHeight lesser than actual height.
        // See https://stackoverflow.com/a/29684471/1216542
        int widthSpec = EstimateSpec.getSizeWithMode(getLayoutConfig().width, EstimateSpec.PRECISE);
        int heightSpec = EstimateSpec.getSizeWithMode(0, EstimateSpec.UNCONSTRAINT);
        estimateSize(widthSpec, heightSpec);
        final int fromY = getEstimatedHeight();
        AnimatorValue marginAnimator = new AnimatorValue();
        marginAnimator.setValueUpdateListener((animatorValue, v) -> {
            Banner.this.setTranslationY(-fromY * 2 * (1 - v));

        });
        final AnimatorGroup animatorSet = new AnimatorGroup();
        animatorSet.runParallel(marginAnimator);
        animatorSet.setDelay(delay);
        animatorSet.setDuration(ANIM_DURATION_SHOW);
        animatorSet.setStateChangedListener(mAnimatorListener);
        animatorSet.start();
    }

    @Override
    public void dismiss() {
        dismiss(0);
    }

    @Override
    public void dismiss(long delay) {
        final int toY = getEstimatedHeight();
        AnimatorValue marginAnimator = new AnimatorValue();
        marginAnimator.setValueUpdateListener((animatorValue, v) -> {
                    Banner.this.setTranslationY(-toY * v);
                }
        );
        final AnimatorGroup animatorSet = new AnimatorGroup();
        animatorSet.runParallel(marginAnimator);
        animatorSet.setDelay(delay);
        animatorSet.setDuration(ANIM_DURATION_DISMISS);
        animatorSet.setStateChangedListener(mAnimatorListener);
        animatorSet.start();
    }

    private final StateChangedListener mAnimatorListener = new StateChangedListener() {
        @Override
        public void onStart(Animator animator) {
            if (animator.getDuration() == ANIM_DURATION_SHOW) {
                setVisibility(VISIBLE);
            } else {
                new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                    @Override
                    public void run() {
                        setVisibility(HIDE);
                    }
                }, animator.getDuration());
            }
        }

        @Override
        public void onStop(Animator animator) {
        }

        @Override
        public void onCancel(Animator animator) {
        }

        @Override
        public void onEnd(Animator animator) {
            if (getVisibility() == VISIBLE) {
                dispatchOnShow();
            } else {
                dispatchOnDismiss();
            }
        }

        @Override
        public void onPause(Animator animator) {
        }

        @Override
        public void onResume(Animator animator) {
        }
    };

    private void dispatchOnShow() {
        if (mOnShowListener != null) {
            mOnShowListener.onShow();
        }
    }

    private void dispatchOnDismiss() {
        if (mOnDismissListener != null) {
            mOnDismissListener.onDismiss();
        }
    }

    /**
     * Calculates the horizontal padding of the inner container.
     *
     * @return the total horizontal padding in pixels
     */
    private int getContainerHorizontalPadding() {
        return mContentContainer.getPaddingStart() + mContentContainer.getPaddingEnd();
    }

    private void setContainerPadding(int start, int top, int end) {
        mContentContainer.setPaddingRelative(
                start != -1 ? start : mContentContainer.getPaddingStart(),
                top != -1 ? top : mContentContainer.getPaddingTop(),
                end != -1 ? end : mContentContainer.getPaddingEnd(), 0);
    }

    /**
     * Retrieves a dimensional for a particular resource ID for use as a size in raw pixels.
     *
     * @param dimenRes the dimension resource identifier
     * @return Resource dimension value multiplied by the appropriate metric and truncated to
     * integer pixels.
     */
    private int getDimen(int dimenRes) {
        int dimes = 0;
        try {
            int vp = getContext().getResourceManager().getElement(dimenRes).getInteger();
            DisplayAttributes da = DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getAttributes();
            dimes = Math.round(da.densityPixels * vp);
        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
        return dimes;
    }

    private int getDimenValue(int dimen) {
        int dimes = 0;
        DisplayAttributes da = DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getAttributes();
        dimes = Math.round(da.densityPixels * dimen);
        return dimes;
    }

    public static class Builder {
        private Context                           mContext;
        private ComponentContainer                mParent;
        private int                               mChildIndex;
        private LayoutConfig                      mParams;
        private int                               mId;
        private int                               mIcon;
        private int                               mLeftRippleResId;
        private int                               mRightRippleResId;
        private String                            mMessageText;
        private String                            mLeftBtnText;
        private String                            mRightBtnText;
        private boolean                           mIsPng;
        private BannerInterface.OnClickListener   mLeftBtnListener;
        private BannerInterface.OnClickListener   mRightBtnListener;
        private BannerInterface.OnDismissListener mOnDismissListener;
        private BannerInterface.OnShowListener    mOnShowListener;

        /**
         * Creates a builder for a banner that uses the default banner style (either specified in
         * the app theme or in this library).
         * <p>
         * within the parent {@code context}'s theme.
         * </p>
         *
         * @param context the parent context
         */
        public Builder(Context context) {
            mContext = context;
        }

        /**
         * Sets the {@link ComponentContainer} that will be a parent view for this banner.
         * <p>
         * <strong>Note:</strong> the banner will be added as a first child to this view. To
         * specify an index use {@link #setParent(ComponentContainer, int)}.
         * </p>
         *
         * @param parent the parent view to display the banner in
         * @return the {@link Builder} object to chain calls
         * @see #setParent(ComponentContainer, int)
         * @see #setParent(ComponentContainer, int, ComponentContainer.LayoutConfig)
         */
        public Builder setParent(ComponentContainer parent) {
            setParent(parent, 0);
            return this;
        }

        /**
         * Sets the {@link ComponentContainer} that will be a parent view for this banner and specify
         * banner's index in the parent view.
         *
         * @param parent the parent view to display the banner in
         * @param index  the position at which to add the banner or -1 to add last
         * @return the {@link Builder} object to chain calls
         * @see #setParent(ComponentContainer)
         * @see #setParent(ComponentContainer, int, ComponentContainer.LayoutConfig)
         */
        public Builder setParent(ComponentContainer parent, int index) {
            setParent(parent, index, new ComponentContainer.LayoutConfig(
                    LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_CONTENT));
            return this;
        }

        /**
         * Sets the {@link ComponentContainer} that will be a parent view for this banner and specify
         * banner's index in the parent view.
         *
         * @param parent the parent view to display the banner in
         * @param index  the position at which to add the banner or -1 to add last
         * @param params the layout parameters to set on the banner
         * @return the {@link Builder} object to chain calls
         * @see #setParent(ComponentContainer)
         * @see #setParent(ComponentContainer, int)
         */
        public Builder setParent(
                ComponentContainer parent, int index, LayoutConfig params) {
            mParent = parent;
            mChildIndex = index;
            mParams = params;
            return this;
        }

        /**
         * Sets the identifier for this banner. The identifier should be a positive number.
         *
         * @param id A number used to identify the banner
         * @return the {@link Builder} object to chain calls
         */
        public Builder setId(int id) {
            mId = id;
            return this;
        }

        public Builder setIcon(int iconId, boolean isPng) {
            mIcon = iconId;
            mIsPng = isPng;
            return this;
        }

        /**
         * Sets the message to display in the banner using the given resource id.
         *
         * @param textId textId
         * @return the {@link Builder} object to chain calls
         */
        public Builder setMessage(int textId) {
            try {
                mMessageText = mContext.getResourceManager().getElement(textId).getString();
            } catch (IOException | NotExistException | WrongTypeException e) {
                e.printStackTrace();
            }
            return this;
        }

        /**
         * Sets the message to display in the banner.
         *
         * @param text text
         * @return the {@link Builder} object to chain calls
         */
        public Builder setMessage(String text) {
            mMessageText = text;
            return this;
        }

        /**
         * Sets a listener to be invoked when the left button of the banner is pressed.
         * <p>
         * Usually used for the dismissive action.
         * <p>
         *
         * @param textId      The resource id of the text to display in the left button
         * @param rippleResId rippleResId
         * @param listener    The {@link BannerInterface.OnClickListener} to use
         * @return the {@link Builder} object to chain calls
         */
        public Builder setLeftButton(int textId, int rippleResId, BannerInterface.OnClickListener listener) {
            try {
                String leftText = mContext.getResourceManager().getElement(textId).getString();
                setLeftButton(leftText, rippleResId, listener);
            } catch (IOException | NotExistException | WrongTypeException e) {
                e.printStackTrace();
            }
            return this;
        }

        /**
         * Sets a listener to be invoked when the left button of the banner is pressed.
         * <p>
         * Usually used for the dismissive action.
         * </p>
         *
         * @param text        The text to display in the left button
         * @param rippleResId rippleResId
         * @param listener    The {@link BannerInterface.OnClickListener} to use
         * @return the {@link Builder} object to chain calls
         */
        public Builder setLeftButton(String text, int rippleResId, BannerInterface.OnClickListener listener) {
            mLeftBtnText = text;
            mLeftBtnListener = listener;
            mLeftRippleResId = rippleResId;
            return this;
        }

        /**
         * Sets a listener to be invoked when the right button of the banner is pressed.
         * <p>
         * Usually used for the confirming action.
         * </p>
         *
         * @param textId      The resource id of the text to display in the right button
         * @param rippleResId rippleResId
         * @param listener    The {@link BannerInterface.OnClickListener} to use
         * @return the {@link Builder} object to chain calls
         */
        public Builder setRightButton(int textId, int rippleResId, BannerInterface.OnClickListener listener) {
            setRightButton(mContext.getString(textId), rippleResId, listener);
            return this;
        }

        /**
         * Sets a listener to be invoked when the right button of the banner is pressed.
         * <p>
         * Usually used for the confirming action.
         * </p>
         *
         * @param text        The text to display in the right button
         * @param rippleResId rippleResId
         * @param listener    The {@link BannerInterface.OnClickListener} to use
         * @return the {@link Builder} object to chain calls
         */
        public Builder setRightButton(String text, int rippleResId, BannerInterface.OnClickListener listener) {
            mRightBtnText = text;
            mRightBtnListener = listener;
            mRightRippleResId = rippleResId;
            return this;
        }

        /**
         * Sets a listener to be invoked when the banner is dismissed.
         *
         * @param listener The {@link BannerInterface.OnDismissListener} to use
         * @return the {@link Builder} object to chain calls
         */
        public Builder setOnDismissListener(BannerInterface.OnDismissListener listener) {
            mOnDismissListener = listener;
            return this;
        }

        /**
         * Sets a listener to be invoked when the banner is shown.
         *
         * @param listener The {@link BannerInterface.OnShowListener} to use
         * @return the {@link Builder} object to chain calls
         */
        public Builder setOnShowListener(BannerInterface.OnShowListener listener) {
            mOnShowListener = listener;
            return this;
        }

        /**
         * Creates a {@link Banner} with the arguments supplied to this builder.
         * <p>
         * Calling this method does not display the banner. If no additional processing is
         * needed, {@link #show()} may be called instead to both create and display the banner.
         * </p>
         *
         * @return The banner created using the arguments supplied to this builder
         */
        public Banner create() {
            final Banner banner = new Banner(mContext);
            banner.setId(mId != 0 ? mId : ResourceTable.String_mb_banner);
            banner.setIcon(mIcon, mIsPng);
            banner.setMessage(mMessageText);
            banner.setLeftButton(mLeftBtnText, mLeftBtnListener);
            banner.setLeftButtonsRippleColor(mLeftRippleResId);
            banner.setRightButtonsRippleColor(mRightRippleResId);
            banner.setRightButton(mRightBtnText, mRightBtnListener);
            banner.setOnDismissListener(mOnDismissListener);
            banner.setOnShowListener(mOnShowListener);
            banner.setLayoutConfig(mParams);
            banner.setVisibility(HIDE);
            if (mParent != null) {
                mParent.addComponent(banner, mChildIndex);
            }
            return banner;
        }

        /**
         * Creates a {@link Banner} with the arguments supplied to this builder and immediately
         * displays the banner.
         * <p>
         * Calling this method is functionally identical to:
         * <pre>
         * Banner banner = builder.create();
         * banner.show();</pre>
         * </p>
         *
         * @return The banner created using the arguments supplied to this builder
         */
        public Banner show() {
            final Banner banner = create();
            banner.show();
            return banner;
        }
    }
}
